<?php

	/**
	 * @see Bike_Initial
	 */
	require_once 'Bike/Initial.php';
	
	/**
	 * @see Zend_Db_Select
	 */
	require_once 'Zend/Db/Select.php';
	
	/**
	 * @see Bike_Db_Table
	 */
	require_once 'Bike/Db/Table.php';
	
	
	class Bike_Model_Mapper_Dependence extends Bike_Initial
	{
        const JOIN_INNER = 'Inner';
        const JOIN_LEFT = 'Left';

        const SQL_WILDCARD = Zend_Db_Select::SQL_WILDCARD;
        const SQL_AND = Zend_Db_Select::SQL_AND;
        const SQL_OR = Zend_Db_Select::SQL_OR;

        static private $_joinPriority = array(self::JOIN_INNER => 0, self::JOIN_LEFT => 1);

        protected $_mapperClass;
        //protected $_mapper;
        protected $_name;
        protected $_type;
        protected $_deepening = True;
        protected $_usage = True;
        protected $_parentMapperClass;
        //protected $_parentMapper;
        protected $_parentDbTableAlias;
        protected $_parentJoinFields;
        protected $_joinFields;
        protected $_joinCondition;
        protected $_returnFields;

        static public function getHighestPriorityType()
        {        	$args = func_get_args();
        	$types = array();
        	foreach($args as $type)
        	{        		$types[self::$_joinPriority[$type]] = $type;
        	}
        	krsort($types);
        	return reset($types);
        }

        public function setType($Type)
	    {	    	$constants = $this->getConstants('JOIN_');
	    	if(in_array($Type, $constants))
	    	{	    		$this->_type = $Type;
	    	}
	    	else
	    	{	    		$this->_type = self::JOIN_LEFT;
	    	}
	    	return $this;
	    }

	    public function getType()
	    {
	    	if(is_null($this->_type))
	    	{	    		require_once 'Bike/Model/Exception.php';
	    		throw new Bike_Model_Exception('No dependence type');
	    	}
	    	return $this->_type;
	    }

	    public function isInner()
	    {
	    	return ($this->getType() == self::JOIN_INNER);
	    }

	    public function setDeepening($Deepening)
	    {
	    	$this->_deepening = $Deepening;
	    	return $this;
	    }

	    public function getDeepening()
	    {
	    	return $this->_deepening;
	    }

	    public function setUsage($Usage)
	    {
	    	$this->_usage = $Usage;
	    	return $this;
	    }

	    public function getUsage()
	    {
	    	return $this->_usage;
	    }

	    public function getReadOnly()
	    {	    	return ($this->getType() != self::JOIN_INNER);
	    }

	    public function setMapperClass($MapperClass)
	    {
	    	$this->_mapperClass = $MapperClass;
	    	return $this;
	    }

	    public function getMapperClass()
	    {
	    	if(!$this->_mapperClass)
	    	{	    		require_once 'Bike/Model/Exception.php';
	    		throw new Bike_Model_Exception('No mapper class name');
	    	}
	    	return $this->_mapperClass;
	    }

	    public function setParentMapperClass($MapperClass)
	    {
	    	$this->_parentMapperClass = $MapperClass;
	    	return $this;
	    }

	    public function getParentMapperClass()
	    {
	    	if(!$this->_parentMapperClass)
	    	{
	    		require_once 'Bike/Model/Exception.php';
	    		throw new Bike_Model_Exception('No parent mapper class name');
	    	}
	    	return $this->_parentMapperClass;
	    }

	    public function setName($Name)
	    {
	    	$this->_name = $Name;
	    	return $this;
	    }

	    public function getName()
	    {
	    	if(!$this->_name)
	    	{
	    		require_once 'Bike/Model/Exception.php';
	    		throw new Bike_Model_Exception('No dependance name');
	    	}
	    	return $this->_name;
	    }

	    public function setParentMapper($ParentMapper)
	    {
	    	$this->_parentMapper = $ParentMapper;
	    	return $this;
	    }

	    public function getParentDbTableAlias()
	    {
	    	if(is_null($this->_parentDbTableAlias))
	    	{	    		return $this->getParentMapper()->getTableAlias();
	    	}
	    	return $this->_parentDbTableAlias;
	    }

	    public function setParentDbTableAlias($ParentDbTableAlias)
	    {
	    	$this->_parentDbTableAlias = $ParentDbTableAlias;
	    	return $this;
	    }
	    
	    public function getMapper()
	    {
	    	return call_user_func(array($this->getMapperClass(), 'getInstance'));
	    }

	    public function getParentMapper()
	    {
	    	return call_user_func(array($this->getParentMapperClass(), 'getInstance'));
	    }

	    public function setParentJoinFields($ParentJoinFields)
	    {
	    	$this->_parentJoinFields = $ParentJoinFields;
	    	return $this;
	    }

	    public function getParentJoinFields()
	    {
	    	if(!$this->_parentJoinFields)
	    	{
	    		require_once 'Bike/Model/Exception.php';
	    		throw new Bike_Model_Exception('No parent join fields');
	    	}
	    	return $this->_parentJoinFields;
	    }

	    public function setJoinFields($JoinFields)
	    {
	    	$this->_joinFields = $JoinFields;
	    	return $this;
	    }

	    public function getJoinFields()
	    {
	    	if(!$this->_joinFields)
	    	{
	    		$this->_joinFields = $this->getMapper()->getTablePrimary();
	    	}
	    	return $this->_joinFields;
	    }

	    public function getFieldNamesFromAliases(array $Aliases, $CheckInDb=True)
	    {	    	$fieldNames = array();
	    	foreach($Aliases as $alias => $dbAlias)
	    	{	    		try
	    		{	    			if(is_int($alias))
	    			{	    				$alias = $dbAlias;
	    			}
	    			$fieldNames[$alias] = $this->getFieldNameFromAlias($dbAlias, $CheckInDb);
	    		}
	    		catch(Bike_Model_Exception $e)
	    		{
	    		}
	    	}
	    	return $fieldNames;
	    }
	    
	    public function getAliasPrefix()
	    {
	    	return $this->getName().Bike_Db_Table::getColumnNameGlue();
	    }
	    
	    public function isDependentAlias($Alias)
	    {
	    	return (substr($Alias, 0, strlen($this->getAliasPrefix())) == $this->getAliasPrefix());
	    }
	    
	    public function getFieldNameWithoutAlias($fieldName)
	    {
	    	return substr($fieldName, strlen($this->getAliasPrefix()));
	    }

	    public function getFieldNameFromAlias($Alias, $CheckInDb=True)
	    {
	    	if($this->isDependentAlias($Alias))
	    	{
	    		$fieldName = $this->getFieldNameWithoutAlias($Alias);
	    		$cols = $this->getMapper()->getDbTable()->info(Bike_Db_Table::COLS);
	    		if(!$CheckInDb || in_array($fieldName, $cols))
	    		{	    			return $fieldName;
	    		}
	    		else
	    		{	    			try
	    			{	    				return $this->getMapper()->getDbTable()->getLocalizedFieldName($fieldName);
	    			}
	    			catch(Zend_Db_Table_Exception $e)
	    			{
	    			}
	    		}
	    	}
	    	require_once 'Bike/Model/Exception.php';
	    	throw new Bike_Model_Exception('Can not transform alias "'.$Alias.'" to field name of '.$this->getMapper()->getDbTable()->info(Bike_Db_Table::NAME));
	    }

	    public function getReturnFieldAlias($FieldName, $Alias=0)
	    {
	    	if(is_int($Alias))
	    	{	    		$aliasPrefix = $this->getAliasPrefix();
	    		return $aliasPrefix.$FieldName;
	    	}
	    	return $Alias;
	    }

	    public function setReturnFields($ReturnFields)
	    {	    	$this->_returnFields = $ReturnFields;
	    	return $this;
	    }

	    public function getReturnFields()
	    {
	    	$cols = $this->getMapper()->getDbTable()->info(Bike_Db_Table::COLS);
	    	if(is_null($this->_returnFields))
	    	{	    		$this->_returnFields = $cols;
	    	}
	    	$returnFields = array();
	    	foreach($this->getJoinFields() as $key => $fieldName)
	    	{	    		$returnFields[$this->getReturnFieldAlias($fieldName, $key)] = $fieldName;
	    	}
	    	$ReturnFields = (array) $this->_returnFields;
	    	foreach($ReturnFields as $alias => $fieldName)
	    	{
	    		if(!in_array($fieldName, $cols))
	    		{
		    		try
		    		{		    			$langColumnName = $this->getMapper()->getDbTable()->getLocalizedFieldName($fieldName);
		    			$ReturnFields[$alias] = $langColumnName;
		    		}
		    		catch(Bike_Model_Exception $e)
					{
						require_once 'Bike/Model/Exception.php';
						throw new Bike_Model_Exception('Specified column "'.$fieldName.'" is not in table "'.$this->getMapper()->getDbTable()->getName().'"');
					}
				}
	    	}
	    	foreach($ReturnFields as $alias => $fieldName)
	    	{	    		$returnFields[$this->getReturnFieldAlias($fieldName, $alias)] = $ReturnFields[$alias];
	    	}
	    	return $returnFields;
	    }

	    public function getDbTableNameWithAlias()
	    {	    	return array($this->getDbTableAlias() => $this->getMapper()->getTableName());
	    }

	    public function getDbTableAlias()
	    {	    	return $this->getMapper()->getTableAlias()."_".$this->getName();
	    }

	    public function getDefaultValues($DefaultSource)
	    {	    	$defaultValues = array();
	    	foreach($this->getMapper()->getAllDefaultValues($DefaultSource) as $name => $value)
	    	{	    		$defaultValues[$this->getReturnFieldAlias($name)] = $value;
	    	}
	    	return $defaultValues;
	    }

	    protected function getJoinCondition(Bike_Db_Mapper_Select $Select, $JoinType = 'AND')
		{
			if(is_null($this->_joinCondition))
			{
				$parentDbTableAlias = $this->getParentDbTableAlias();
				$dbTableAlias = $this->getDbTableAlias();
				$parentJoinFields = $this->getParentJoinFields();
				$joinFields = $this->getJoinFields();
	            if(sizeof($this->getParentJoinFields()) != sizeof($this->getJoinFields()))
	            {
	            	require_once 'Bike/Model/Exception.php';
	            	throw new Bike_Model_Exception('Count of join fields are not the same');
	            }
	            if(!sizeof($this->getParentJoinFields()))
	            {	            	require_once 'Bike/Model/Exception.php';
	            	throw new Bike_Model_Exception('Invalid count of join fields');
	            }
	            $condition = array();
	            $parentJoinField = reset($parentJoinFields);
	            $joinField = reset($joinFields);
	            while($parentJoinField && $joinField)
	            {
	            	array_push($condition, join(" = ", array(
	            		$Select->getAdapter()->quoteIdentifier($Select->getFieldNameWithTabAlias($joinField, $dbTableAlias)),
	            		$Select->getAdapter()->quoteIdentifier($Select->getFieldNameWithTabAlias($parentJoinField, $parentDbTableAlias)),
	           		)));
	            	$parentJoinField = next($parentJoinFields);
	            	$joinField = next($joinFields);
	            }
	            $this->_joinCondition = join(' '.$JoinType.' ', $condition);
			}
			return $this->_joinCondition;
		}

		protected function _join(Bike_Db_Mapper_Select $Select)
		{			$methodName = 'join'.$this->getType();
		    $Select->$methodName($this->getDbTableNameWithAlias(), $this->getJoinCondition($Select), $this->getReturnFields());
		}

		protected function _applyChildDependenceOptions($Dependence, &$Options, $NamePrefix)
	    {
	    	$dependenceName = $NamePrefix.$Dependence->getName();
			$Options[$dependenceName]['name'] = $dependenceName;
			$Options[$dependenceName]['parentDbTableAlias'] = $this->getDbTableAlias();
			$Options[$dependenceName]['type'] = $this->getHighestPriorityType($this->getType(), $Dependence->getType());
	    }

	    public function join(Bike_Db_Mapper_Select $Select, $Options, $NamePrefix="")
	    {	    	$this->setOptions((array) @$Options[$NamePrefix.$this->getName()]);
	    	if($this->getUsage())
	    	{
		    	$this->_join($Select);
		    	if($this->getDeepening())
		    	{			    	$dependencesOnly = Null;
			    	if(is_string($this->getDeepening()))
			    	{			    		$dependencesOnly = array($this->getDeepening());
			    	}
			    	if(is_array($this->getDeepening()))
			    	{			    		$dependencesOnly = $this->getDeepening();
			    	}
			    	$namePrefix = $this->getName()."_";
			    	$Dependences = ($Select->isInnerDependencesType()) ? $this->getMapper()->getInnerDependences() : $this->getMapper()->getDependences();
			    	foreach($Dependences as $n => $dependence)
					{
						if($dependencesOnly && !in_array($dependence->getName(), $dependencesOnly))
						{							unset($Dependences[$n]);
							continue;
						}
						$this->_applyChildDependenceOptions($dependence, $Options, $namePrefix);
					}
					$Select->joinDependences($Dependences, $Options, $namePrefix);
				}
			}
	    }

	    public function joinByFields(Bike_Db_Mapper_Select $Select, $Fields, array $Options=null, $NamePrefix="")
	    {
	    	$this->setOptions((array) $Options[$NamePrefix.$this->getName()]);
	    	$fieldNames = $this->getFieldNamesFromAliases($Fields, False);
	    	if(sizeof($fieldNames))
	    	{
		    	$this->setReturnFields($this->getFieldNamesFromAliases($Fields));
		    	$this->_join($Select);
		    	$namePrefix = $this->getName()."_";
		    	$Dependences = $this->getMapper()->getDependences();
		    	foreach($Dependences as $n => $dependence)
				{
					$this->_applyChildDependenceOptions($dependence, $Options, $namePrefix);
				}
				$Select->joinDependencesByFields($Dependences, $Fields, $Options, $namePrefix);
			}
	    }
	}
